#include "reader.hpp"
#include <cstring>
#include "error.hpp"

#define ERROR(x) (throw ReadingError((x), _line, _ccount))

namespace yxml
{
    void Reader::read( Document& document )
    {
        //Ignoring anything before the first tag...
        while( get() != '<' )
        {
            if( eof() )
                ERROR( "Unexpected end of file" );
        }
        unget();
        map <string, string> attributes;
        bool opening, closing;
        string tagName = getTag( attributes, opening, closing );
        //Check if it have a reader tag (<?xml ... >)
        string encoding = "UTF-8";
        if( tagName=="?xml" )
        {
            encoding = attributes["encoding"];
            attributes.clear();
            ignoreSpace();
            tagName = getTag( attributes, opening, closing );
        }
        document.value( tagName );
        document.attributes() = attributes;
        readTag( document );
    }

    void Reader::readTag( Node& node )
    {
        string temp = ignoreSpace();
        int ch = get();
        while( !eof() )
        {
            if( ch == '<' )
            {
                if( ( ch = get() ) == '!' )
                {
                    unget();
                    unget();
                    node.addChild( COMMENT, getComment() );
                }
                else
                {
                    unget();
                    unget();
                    map <string, string> attributes;
                    bool opening, closing;
                    string tagName = getTag( attributes, opening, closing );
                    if( opening )
                    {
                        Node& tag = node.addChild( TAG, tagName );
                        tag.attributes() = attributes;
                        if( !closing )
                            readTag( tag );
                    }
                    else // closing = true && opening = false
                    {
                        if( tagName == node.value() )
                            return;
                    }
                }
            }
            else
            {
                unget();
                node.addChild( TEXT, getText( temp ) );
            }
            string temp = ignoreSpace();
            ch = get();
        }
    }

    string Reader::getText( const string& spaces )
    {
        string text = "";
        int identationLevel = 0;
        if( _textPolicy == IGNORE_IDENTATION )
        {
            size_t endlPos = spaces.find_last_of( '\n' );
            if( endlPos != string::npos )
                identationLevel = spaces.size() - endlPos - 1;
        }
        else if( _textPolicy == NOTHING )
        {
            text += spaces;
        }
        int ch = get();
        while( !eof() )
        {
            if( ch == '<' )
            {
                unget();
                if( _textPolicy == IGNORE_LAST_EXTRA_SPACES || _textPolicy == IGNORE_IDENTATION )
                {
                    for( int i= text.size()-1; i >= 0; --i )
                    {
                        int dh = text[i];
                        if(dh != ' ' && dh != '\t' && dh != '\n')
                        {
                            text.resize(i+1);
                            break;
                        }
                        else if( dh == '\n')
                        {
                            text.resize(i);
                            break;
                        }
                    }
                }

                return text;
            }
            else if( ch == '&' )
            {
                char word[] = {static_cast<char>(get()), static_cast<char>(get()), static_cast<char>(get()), static_cast<char>(get()), 0};
                if( strcmp( word, "amp;" )==0 )
                    text+= '&';
                else if( strcmp( word, "lt;" )==0 )
                {
                    unget();
                    text+= '<';
                }
                else if( strcmp( word, "gt;" )==0 )
                {
                    unget();
                    text+= '>';
                }
                else;// TODO (Tales#1#): Throw an exception here
            }
            else if( _textPolicy == IGNORE_ALL_EXTRA_SPACES && ( ch==' '||ch=='\n'||ch=='\t' ) )
            {
                text += ' ';
                ignoreSpace();
            }
            else if( _textPolicy == IGNORE_IDENTATION && ch == '\n' )
            {
                text += '\n';
                for( int i=0; i<identationLevel; ++i )
                {
                    int dh = get();
                    if( dh != ' ' && dh != '\t' )
                    {
                        unget();
                        break;
                    }
                }
            }
            else
            {
                text += ch;
            }
            ch = get();
        }
        ERROR( "Unexpected EOF" );
    }


    string Reader::getComment()
    {
        if( get() == '<' && get() == '!' && get() == '-' && get() == '-' )
        {
            string comment = "";
            int ch = get();
            bool endComent = false;
            while( !eof() )
            {
                int minusCount = 0;
                while( ch == '-' )
                {
                    ++minusCount;
                    ch = get();
                    if( eof() )
                        ERROR( "Unexpected EOF" );
                }
                while( minusCount > 2 )
                {
                    comment += '-';
                    --minusCount;
                }
                if( minusCount ==2 && ch == '>' )
                {
                    endComent = true;
                    break;
                }
                while( minusCount > 0 )
                {
                    comment += '-';
                    --minusCount;
                }
                comment += ch;
                ch = get();
            }
            if( !endComent )
                ERROR( "Unexpected EOF" );
            return comment;
        }
        else ERROR( "Sintax Error: Expected Comment" );
    }


    string Reader::getTag( map<string,string> &attributes, bool& openning, bool& closing )
    {
        if( get() != '<' ) ERROR( "Tag Expected" );
        bool headering = false;
        closing = false;
        openning = true;
        int ch;
        switch( ch = get() )
        {
        case '?':
            headering = true;
            break;
        case '/':
            closing = true;
            openning = false;
            break;
        default:
            unget();
        }
//        ch = get();
//        if( !isalpha( ch ) )
//            ERROR( "expected name");
        string tagName = getWord();
        ignoreSpace();
        ch = get();
        while( ch != '>' )
        {
            if( isalpha( ch ) )
            {
                unget();
                string attrName = getWord();
                ignoreSpace();
                match( '=' );
                ignoreSpace();
                string attrValue = getString();
                attributes[ attrName ] = attrValue;
            }
            else if( ch == '/' )
            {
                if( closing || headering )
                    ERROR( "Sintax Error" );
                match( '>' );
                closing = true;
                break;
            }
            else if( ch == '?' )
            {
                if( !headering ) ERROR( "Unexpectes character '?'" );
            }
            else
            {
                ERROR( "Expected attribute or '>'" );
            }
            ignoreSpace();
            ch = get();
            if( eof() )
                ERROR( "Unexpected end of file" );
        }
        if( headering )
            tagName.insert( 0, "?" );
        return tagName;
    }

    string Reader::getWord()
    {
        string word = "";
        int ch = get();
        while ( isalnum( ch ) || ch == '_' || ch == '.' )
        {
            word += ch;
            ch = get();
            if( eof() )
                ERROR( "Unexpected EOF" );
        }
        unget();
        return word;
    }

    string Reader::getString()
    {
        match( '"' );
        string s = "";
        int ch;
        while( ( ch = get() ) !=  '"' )
        {
            if( eof() )
                ERROR( "Unexpected EOF" );
            s += ch;
        }
        unget();
        match( '"' );
        return s;
    }

    void Reader::match( char ch )
    {
        if( get() !=  ch )
        {
            stringstream s;
            s << "Expected '" << ch << "'";
            ERROR( s.str() );
        }
    }

    string Reader::ignoreSpace()
    {
        string spaces = "";
        int ch = get();
        while ( ch == ' ' || ch == '\n' || ch == '\t' )
        {
            spaces += ch;
            ch = get();
            if( eof() )
                ERROR( "Unexpected EOF" );
        }
        unget();
        return spaces;
    }
    int Reader::get()
    {
        int ch = _in.get();

        if( ch == '\n' )
        {
            _ccount = 0;
            ++_line;
        }
        else
            ++_ccount;
        return ch;
    }

    void Reader::unget()
    {
        _in.unget();

        if( _in.peek() == '\n' )
        {
            _ccount = 0;
            --_line;
        }
        else
            --_ccount;
    }

    bool Reader::eof()
    {
        return _in.eof();
    }

    Reader::Reader( istream& in ):_in( in )
    {
        _line = 1;
        _ccount = 0;
        _textPolicy = IGNORE_ALL_EXTRA_SPACES;
        _commentPolicy = IGNORE_ALL_EXTRA_SPACES;
    }
}
